package com.ticket.sass.admin.business.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.ticket.sass.admin.business.FilmScheduleBusiness;
import com.ticket.sass.admin.dto.input.FilmSortInput;
import com.ticket.sass.admin.dto.input.SaleFilmListInput;
import com.ticket.sass.admin.dto.input.film.FilmScheduleListInput;
import com.ticket.sass.admin.dto.input.film.SaleChannelInput;
import com.ticket.sass.admin.dto.input.film.SalePriceInput;
import com.ticket.sass.admin.dto.input.pricescheme.PriceSchemeInput;
import com.ticket.sass.admin.dto.output.film.DateScheduleOutput;
import com.ticket.sass.admin.dto.output.film.FilmScheduleListOutput;
import com.ticket.sass.admin.dto.output.film.SaleFilmListOutput;
import com.ticket.sass.admin.dto.output.pricecheme.PriceSchemeSimpOutput;
import com.ticket.sass.admin.model.PageModel;
import com.ticket.sass.admin.service.impl.*;
import com.ticket.sass.admin.util.CinemaIdUtil;
import com.ticket.sass.admin.util.ModelUtil;
import com.ticket.sass.admin.util.RedisCacheUtil;
import com.ticket.sass.admin.util.ScheduleDataUtil;
import com.ticket.sass.common.entity.tenant.FilmScheduleEntity;
import com.ticket.sass.common.exception.enums.ErrorCode;
import com.ticket.sass.common.exception.exception.BadRequestException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.modelmapper.TypeToken;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author ywb
 * @date 2022-09-27 10:10
 */
@Slf4j
@Service
@AllArgsConstructor
public class FilmScheduleBusinessImpl implements FilmScheduleBusiness {
    final ModelUtil modelUtil;
    final FilmServiceImpl filmService;
    final HallServiceImpl hallService;
    final RedisCacheUtil redisCacheUtil;
    final CinemaServiceImpl cinemaService;
    final FilmScheduleServiceImpl service;
    final ScheduleDataUtil scheduleDataUtil;
    final FilmVersionServiceImpl filmVersionService;
    final PriceSchemeBusinessImpl priceSchemeBusiness;
    final FilmCinemaMapServiceImpl filmCinemaMapsService;
    final OrderFilmSaleMapServiceImpl orderFilmSaleMapService;
    final FilmScheduleSaleChannelServiceImpl filmScheduleSaleChannelService;

    @Override
    public PageModel<FilmScheduleListOutput> list(FilmScheduleListInput input) {
        Long cinemaId = CinemaIdUtil.getCinemaIdFromRequest();
        IPage<FilmScheduleEntity> page = service.iPage(input, cinemaId);
        List<FilmScheduleListOutput> outputs = modelUtil.listToList(
            page.getRecords(),
            new TypeToken<List<FilmScheduleListOutput>>(){}.getType()
        );
        
        List<Long> ids = outputs.stream().map(FilmScheduleListOutput::getId).collect(Collectors.toList());
        
        Map<Long, List<Short>> channelsByFilmScheduleIds = filmScheduleSaleChannelService.getChannelsByFilmScheduleIds(ids);

        
        
        List<PriceSchemeInput> collect = outputs.stream().map(PriceSchemeInput::new).collect(Collectors.toList());
        Map<Long, PriceSchemeSimpOutput> longListMap = priceSchemeBusiness.matchLabModels(collect);

        
        
        outputs.forEach(o -> {
            o.setSaleChannelType(channelsByFilmScheduleIds.getOrDefault(o.getId(), new ArrayList<>()));
            o.setPriceSchemes(longListMap.getOrDefault(o.getId(), null));
        });
        
        
        return new PageModel<>(page.getTotal(), outputs);
    }
    
    
    @Override
    public void refresh() {
        Long cinemaId = CinemaIdUtil.getCinemaIdFromRequest();
        refreshSchedule(cinemaId);
    }
    
    
    public void refreshSchedule(Long cinemaId) {
        
        Long tenantId = cinemaService.getTenantIdByCinemaId(cinemaId);
        
        // 构造锁的 key
        String lockKey = "refresh:schedule:" + cinemaId;
        
        
        try {
            // 尝试获取锁，设置锁的超时时间为 10 秒
            if (redisCacheUtil.lock(lockKey, "locked", 10000)) {
                List<FilmScheduleEntity> filmScheduleEntities = scheduleDataUtil.getSchedules(cinemaId);
                
                if (filmScheduleEntities.size() == 0){
                    return;
                }
                
                List<String> filmCodes = filmScheduleEntities.stream()
                    .map(FilmScheduleEntity::getFilmCode)
                    .distinct() // 去重操作
                    .collect(Collectors.toList());
                Map<String, Long> filmIdCodeMap = filmVersionService.getFilmIdsByCodes(filmCodes);
                
                // 更新该影院下 影片Id
                service.updateFilmIdByFilmCodeAndCinemaId(filmIdCodeMap, cinemaId);
                
                List<String> hallCodes = filmScheduleEntities.stream()
                    .map(FilmScheduleEntity::getHallCode)
                    .distinct() // 去重操作
                    .collect(Collectors.toList());
                Map<String, Long> hallIdCodeMap = hallService.getHallIdsByCodes(hallCodes, cinemaId, tenantId);
        
                
                // 更新该影院下 影厅Id
                service.updateHallIdByHallCodeAndCinemaId(hallIdCodeMap, cinemaId);
                
                // 获取当前未开场的排期数据
                List<FilmScheduleEntity> unStartedList = service.selectUnstartedSchedulesByCinemaId(cinemaId);
                
                // 同步过来的所有数据
                List<String> allSessionCode = filmScheduleEntities.stream()
                    .map(FilmScheduleEntity::getSessionCode)
                    .distinct() // 去重操作
                    .toList();
                
                // 开场数据 筛选 如果同步数据里没有 即排期失效
                List<Long> filmScheduleIdToClear = unStartedList.stream()
                    .filter(schedule -> !allSessionCode.contains(schedule.getSessionCode()))
                    .map(FilmScheduleEntity::getId)
                    .collect(Collectors.toList());
                
                // 过滤已经存在的数据
                List<String> sessionCode = service.sessionCode(cinemaId);
                filmScheduleEntities = filmScheduleEntities.stream().filter(x -> !sessionCode.contains(x.getSessionCode())).collect(Collectors.toList());
                
                filmScheduleEntities.forEach(x -> {
                    x.setCinemaId(cinemaId);
                    x.setFilmId(filmIdCodeMap.getOrDefault(x.getFilmCode(), 0L));
                    x.setHallId(hallIdCodeMap.getOrDefault(x.getHallCode(), 0L));
                });
                // 新增不存在的数据
                service.saveBatch(filmScheduleEntities);
                
                // 如果存在要删除的 则删除
                if (!filmScheduleIdToClear.isEmpty()) {
                    filmScheduleSaleChannelService.removeByScheduleIds(filmScheduleIdToClear);
                }
                
                // 新增开售渠道
                if (!filmScheduleEntities.isEmpty()){
                    List<Long> filmScheduleIds = filmScheduleEntities.stream().map(FilmScheduleEntity::getId).collect(Collectors.toList());
                    filmScheduleIds.removeAll(filmScheduleIdToClear);
                    filmScheduleSaleChannelService.addBatchRelationWithDefaultType(filmScheduleIds, cinemaId, tenantId);
                }
                
                // 确保 绑定
                List<String> list = service.filmCodeByNow(cinemaId, filmCodes);
                // 更新该影院下 影片Id
                service.updateFilmIdByFilmCodeAndCinemaId(filmVersionService.getFilmIdsByCodes(list), cinemaId);
                
            }
        } finally {
            // 释放锁
            redisCacheUtil.unlock(lockKey);
        }
    }
    
    @Override
    public void setSaleChannel(SaleChannelInput input) {
        filmScheduleSaleChannelService.updateChannels(input.getId(), input.getType());
    }
    
    @Override
    public PageModel<SaleFilmListOutput> saleFilmList(SaleFilmListInput input) {
        List<SaleFilmListOutput> outputs = modelUtil.listToList(
            service.getOnSaleFilms(input),
            new TypeToken<List<SaleFilmListOutput>>(){}.getType()
        );
        
        List<Long> filmIds = outputs.stream().map(SaleFilmListOutput::getFilmId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
        
        Map<Long, String> longStringMap = filmService.mapCoverByIds(filmIds);
        
        Map<Long, BigDecimal> totalAmountByFilmIds = orderFilmSaleMapService.getTotalAmountByFilmIds(filmIds);
        
        Map<Long, Integer> totalSaleInfoByFilmIds = orderFilmSaleMapService.getTotalSaleInfoByFilmIds(filmIds);
        
        Map<Long, Integer> filmSortMapByFilmIds = filmCinemaMapsService.getFilmSortMapByFilmIds(filmIds);
        
        outputs.forEach(o -> {
            o.setSaleTicketAmount(totalAmountByFilmIds.getOrDefault(o.getFilmId(), new BigDecimal("0.0")));
            o.setSaleTicketNum(totalSaleInfoByFilmIds.getOrDefault(o.getFilmId(), 0));
            o.setFilmCover(longStringMap.getOrDefault(o.getFilmId(), ""));
            o.setSort(filmSortMapByFilmIds.getOrDefault(o.getFilmId(), 0));
        });
        outputs.sort(Comparator.comparingInt(o -> o.getSort()));
        return new PageModel<>((long) outputs.size(), outputs);
    }
    
    @Override
    public void setSalePrice(SalePriceInput input) {
        validateId(input.getId());
        
        service.updateSalePriceById(input.getId(), input.getSalePrice());
    }
    
    @Override
    public List<DateScheduleOutput> dateList() {
        // 筛选今天 0 点 分组
        List<FilmScheduleEntity> startTime = service.list(new QueryWrapper<FilmScheduleEntity>().ge("start_time", LocalDate.now()));
        Map<Long, List<Short>> channelsByFilmScheduleIds = filmScheduleSaleChannelService.getChannelsByFilmScheduleIds(startTime.stream().map(FilmScheduleEntity::getId).collect(Collectors.toList()));
//
        Map<LocalDate, List<FilmScheduleEntity>> mapData = startTime.stream()
            .collect(Collectors.groupingBy(FilmScheduleEntity::getStartTimeLocalDate));
        
        List<DateScheduleOutput> scheduleList = new ArrayList<>();
        
        
        List<PriceSchemeInput> collect = startTime.stream().map(PriceSchemeInput::new).collect(Collectors.toList());
        Map<Long, PriceSchemeSimpOutput> longListMap = priceSchemeBusiness.matchLabModels(collect);
        
        for (Map.Entry<LocalDate, List<FilmScheduleEntity>> entry : mapData.entrySet()) {
            DateScheduleOutput scheduleOutput = new DateScheduleOutput(entry.getKey());
            
            List<FilmScheduleListOutput> dateScheduleList = entry.getValue().stream()
                .map(entity -> {
                    FilmScheduleListOutput filmScheduleListOutput = modelUtil.mapToMap(entity, FilmScheduleListOutput.class);
                    filmScheduleListOutput.setSaleChannelType(channelsByFilmScheduleIds.getOrDefault(filmScheduleListOutput.getId(), new ArrayList<>()));
                    filmScheduleListOutput.setPriceSchemes(longListMap.getOrDefault(filmScheduleListOutput.getId(), null));
                    return filmScheduleListOutput;
                })
                .sorted(Comparator.comparing(FilmScheduleListOutput::getStartTime))
                .collect(Collectors.toList());
            
            scheduleOutput.setList(dateScheduleList);
            scheduleList.add(scheduleOutput);
        }
        scheduleList.sort(Comparator.comparing(DateScheduleOutput::getDate));
        return scheduleList;
    }
    
    @Override
    public void setSort(FilmSortInput input) {
        List<Long> filmIds = service.getOnSaleFilms(new SaleFilmListInput()).stream().map(FilmScheduleEntity::getFilmId).collect(Collectors.toList());
  
        filmCinemaMapsService.saveOrUpdateFilmSort(input.getFilmId(), input.getSort(), filmIds);
    }
    
    private void validateId(Long id) {
        if (service.isEmpty(id)) {
            throw new BadRequestException(
                ErrorCode.RECORD_NOT_EXIST,
                ErrorCode.RECORD_NOT_EXIST.getReasonPhrase()
            );
        }
    }
}
